package mint.modules.generator;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import org.antlr.runtime.tree.Tree;

import mint.MintLexer;
import mint.modules.symbol.Symbol;
import mint.modules.symbol.SymbolFactory;
import mint.modules.symbol.SymbolType;
import mint.modules.type.Type;
import mint.modules.type.TypeId;

public class MethodDeclarationNode extends Scope implements MintNode {
	private String name;
	private String modifier;
	private Type type;	
	private List<String> errorList;
	private List<FormalParameterNode> parameters;
	private int line;
	private int localsSize;
	private int stackSize;
	private BlockNode block;	
	
	public MethodDeclarationNode(Scope theParent, Tree methodNode) {
		super(theParent, theParent.containerClass);
		Tree methodHeader = methodNode.getChild(0);
		Tree blockTree = methodNode.getChild(1);
		
		CounterManager.getInstance().initializeLocal();
		errorList = new ArrayList<String>();
		processHeader(methodHeader);
		block = new BlockNode(this, blockTree);		
		line = methodNode.getLine();
		localsSize = CounterManager.getInstance().getLocalNumber();
		stackSize = CounterManager.getInstance().incrementParameter();
		if (containerClass == null)
			System.out.println("ALERTAA");
	}
	
	public String getName()
	{
		return name;
	}
	
	public String getModifier()
	{
		return modifier;
	}
	
	public Type getType()
	{
		return type;
	}
	
	public int getParameterListSize()
	{
		return parameters.size();
	}
	
	public FormalParameterNode getParameter(int i)
	{
		if(i >= 0 && i < parameters.size())
			return parameters.get(i);
		else
			return null;
	}
	
	@Override
	public void testSemantic() {
		if (type.getTypeId() == TypeId.Object || type.getTypeId() == TypeId.ObjectArray)
		{
			Symbol symbol = searchSymbol(type.getName(), getContainerClass(), false);
			
			if (symbol == null)
				errorList.add(line + ": " + type.getName() + " doesn't exists on this scope");
			else
				if (symbol.getSymbolType() != SymbolType.Class)
					errorList.add(line + ": " + type.getName() + " isn't a type name");
		}
		for(FormalParameterNode param : parameters)
			param.testSemantic();
		block.testSemantic();
	}

	@Override
	public List<String> getSemanticErrors() {
		for(FormalParameterNode param : parameters)
			errorList.addAll(param.getSemanticErrors());
		errorList.addAll(block.getSemanticErrors());
		return errorList;
	}

	@Override
	public void emit(Formatter output) {
		output.format(";Method %s\n", name);
		if (name.equals("readyGo"))
		{
			output.format(".method public static main([Ljava/lang/String;)V\n");
		} else
		{			
			output.format(".method %s %s(", modifier, name);
			for (FormalParameterNode param : parameters)
			{
				param.emit(output);
			}
			output.format(")%s\n", type.emit());
		}
		output.format(".limit stack %d\n", localsSize * 2 + stackSize);
		output.format(".limit locals %d\n", localsSize);
		block.emit(output);
		
		if (type.equals(Type.intType) || type.equals(Type.booleanType))
		{
			output.format("iconst_0\n");
			output.format("ireturn\n");
		} else if (type.equals(Type.floatType))
		{
			output.format("fconst_0\n");
			output.format("freturn\n");
		} else if (type.equals(Type.voidType))
			output.format("return\n");
		else
		{
			output.format("aconst_null\n");
			output.format("areturn\n");
		}
		output.format(".end method\n");
		output.format(";End method %s\n", name);
	}

	@Override
	public int getLine() {
		return line;
	}

	@Override
	public void printSymbolTable(Formatter output, int tabSize) {
		block.printSymbolTable(output, tabSize + 1);
	}
	
	private void processHeader(Tree methodHeader) {
		Tree firstChild = methodHeader.getChild(0);
		
		if (firstChild.getType() == MintLexer.MODIFIER)
		{
			modifier = firstChild.getChild(0).getText();			
			type = Type.getTypeFromTree(methodHeader.getChild(1));
			name = methodHeader.getChild(2).getText();
			processFormalParameterList(methodHeader.getChild(3));
		}
		else
		{
			modifier = "private";
			type = Type.getTypeFromTree(methodHeader.getChild(0));
			name = methodHeader.getChild(1).getText();
			processFormalParameterList(methodHeader.getChild(2));
		}
		
	}

	private void processFormalParameterList(Tree parameterTree) {
		parameters = new ArrayList<FormalParameterNode>();
		
		for (int i = 0; i < parameterTree.getChildCount(); i++)
		{
			FormalParameterNode node = new FormalParameterNode(parameterTree.getChild(i), this);
			
			parameters.add(node);
			addSymbol(SymbolFactory.createParameterSymbol(node, CounterManager.getInstance().getNewLocal()));
		}
	}
}
